"""
服务器: 
    功能: 业务逻辑处理
    模型: 多进程tcp并发
模块:
    socket # 套接字
    Process # 多进程
    signal # 系统托管,处理僵尸进程
    
ps: 该网络模型,在window下可能无法运行
"""

from socket import *
from multiprocessing import Process
import signal
import sys
from time import sleep
from mysql import *


class Enums:
    """枚举及相关字段
    :REGISTER 注册用户
    :LOGIN 登录
    :EXIT_ 退出
    :LOOK 查单词
    :HISTORY 历史记录
    :ADDR 根据自己情况绑定ip和端口
    """
    REGISTER = 'R'
    LOGIN = 'L'
    LOOK = 'Q'
    EXIT_ = 'E'
    HISTORY = 'H'
    ADDR = ("0.0.0.0", 18693)


class DictServer(Process):
    """多线程模型"""

    def __init__(self, connfd, addr, db):
        super().__init__()
        self.__connfd = connfd
        self.__addr = addr
        self.__db = db

        # 每个进程单独生成一个游标方便操作数据库, 这里是没有返回游标的返回值,在mysql里面有就够了
        self.__db.cursor_()

    def run(self):
        self.__start_()

    def __start_(self):
        while True:
            data = self.__connfd.recv(1024).decode()
            print(self.__connfd.getpeername(), ":", data)  # 测试链接
            if not data or data == Enums.EXIT_:
                sys.exit("有客户端退出了!")
            elif data[0] == Enums.REGISTER:  # 注册
                self.__do_register(data)
            elif data[0] == Enums.LOGIN:  # 登录
                self.__do_login(data)
            elif data[0] == Enums.LOOK:  # 查询
                self.__do_query(data)
            elif data[0] == Enums.HISTORY:  # 记录
                self.__do_log(data)

    def __do_log(self, data):
        """历史记录"""
        tmp = data.split(' ')
        name = tmp[1]
        da = self.__db.do_log(name)
        # print(da)
        if not da:
            self.__connfd.send("没有历史记录".encode())
            return
        self.__connfd.send(b'ok')

        # 返回的是一个二维元组,遍历发送
        for i in da:
            msg = "%s %-16s %s" % i  # 由于是元组,会自动依次赋值
            sleep(0.08)  # 防止沾包, 卡一下时间
            self.__connfd.send(msg.encode())

        sleep(0.08)
        self.__connfd.send(b'##')  # 结束标志

    def __do_query(self, data):
        """查单词"""
        tmp = data.split(' ')
        name = tmp[1]
        word = tmp[2]
        ret = self.__db.query(word)
        if not ret:
            self.__connfd.send("单词未找到!".encode())
        self.__connfd.send(f"{word}->: {ret}".encode())
        self.__db.insert_hist(name, word)  # 插入历史记录

    def __do_login(self, data):
        """登录处理"""
        tmp = data.split(' ')
        name = tmp[1]
        passwd = tmp[2]
        ret = self.__db.login(name, passwd)
        if ret == 'ok':
            self.__connfd.send(ret.encode())
        else:
            self.__connfd.send(ret.encode())

    def __do_register(self, data):
        """注册处理"""
        tmp = data.split(' ')
        name = tmp[1]
        passwd = tmp[2]
        # 验证和注册直接在mysql类里面解决, 这里是为了降低耦合性, 如果返回ok则成功, 否则相反
        ret = self.__db.register(name, passwd)
        if ret == 'ok':
            self.__connfd.send(ret.encode())  # 注册成功
        else:
            self.__connfd.send(ret.encode())  # 失败


# %% 搭建网络


class Main:
    """初始化套接字,创建链接"""

    def __init__(self):
        self.__sockfd = socket()
        self.__sockfd.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)  # 端口复用
        self.__sockfd.bind(Enums.ADDR)
        self.__sockfd.listen(5)  # 监听

        signal.signal(signal.SIGCHLD, signal.SIG_IGN)  # 进程托管

        self.__db = DataBase(host="localhost", port=3306,
                             user="debian-sys-maint", passwd="djknQTX2Egm1ZJVq",
                             charset="utf8", database="dict")

        print("网络和数据库服务启动成功.")

    def start(self):
        """开启链接服务"""
        # 创建链接
        while True:
            try:
                c, addr = self.__sockfd.accept()
                print("有客户端连接: ", addr)
            except KeyboardInterrupt:
                c.send(b'E')
                self.__sockfd.close()  # 关闭当前链接
                self.__db.close()
                sys.exit("服务器断开链接!")
            except Exception as e:
                print("看样子出问题了-_-: ", e)
                continue

            # 创建子进程
            p = DictServer(c, addr, self.__db)
            p.daemon = True  # 主进程退则全退
            p.start()


# %% 测试
if __name__ == "__main__":
    m = Main()
    m.start()
